package com.g1.screens.services;

import java.util.Random;

import org.newdawn.slick.GameContainer;
import org.newdawn.slick.Graphics;
import org.newdawn.slick.Image;
import org.newdawn.slick.SlickException;
import org.newdawn.slick.Sound;
import org.newdawn.slick.geom.Vector2f;
import org.newdawn.slick.state.StateBasedGame;

import com.g1.screens.enumerations.Animation;
import com.g1.screens.enumerations.BlockType;
import com.g1.screens.model.Block;

public class Match3Service {
	
	private Animation state;
	
	public Animation getState() {
		return state;
	}

	public final static int ROWS = 6;
	public final static int COLUMNS = 8;
	public final static Vector2f position = new Vector2f(355, 45);
	private Image blocksResource;
	private Random r;
	boolean isPlayerTurn;
	
	private Sound wrong;
	
	private Block[][] blocks;
	private Block[] selectedBlock; // Max 2 blocks can be selected
	
	private Block generateRandomBlock(final int column, final int row, final Image blocksResource){
		Block b = new Block(column, row, BlockType.getId(r.nextInt(BlockType.values().length)), blocksResource);
		b.setX(column * BlockType.width + position.x);
		b.setY(row * BlockType.height + position.y);
		return b;
	}
	
	private void move(Block b, int column, int row){
		
	}
	
	public Match3Service() throws SlickException {
		wrong = new Sound("res/data/sound/wrong.wav");
		r = new Random();
		isPlayerTurn = true;
		blocksResource = new Image("data/blocks.png");
		blocks = new Block[COLUMNS][ROWS];
		for(int i = 0; i<ROWS; i++){
			for(int j = 0; j<COLUMNS; j++){
				blocks[j][i] = generateRandomBlock(j,i, blocksResource);
			}
		}
		selectedBlock = new Block[2];
		state = Animation.IDLE;
	}
	
	/**
	 * this method can clear the 2 selected blocks if the selected block is itself Or if the user is pressing in another place than the match3 arena
	 * otherwise the first click will set the first select block then the second click will set the second block.
	 * Return true when the user a done two distinct selections
	 * @param mouseX
	 * @param mouseY
	 */
	private boolean mouseClickAction(final int mouseX, final int mouseY){
		Block b = retrievedBlockFromClick(mouseX, mouseY);
		System.out.println("== b = "+b+" ==");
		if(b != null){
			b.setSelected(true);
			if(selectedBlock[0] == null) {
				selectedBlock[0] = b;
			} else if(selectedBlock[0] == b){
				clearSelectedBlock();
			} else {
				if(isContiguousBlock(selectedBlock[0],b)) {
					selectedBlock[1] = b;
				} else {
					selectedBlock[0] = b;
				}
			}
		} else {
			clearSelectedBlock();
		}
		return selectedBlock[0]!=null && selectedBlock[1] != null;
	}
	
	
	
	public void switchSelectedBlocksAction(){
		Block b1 = selectedBlock[0];
		Block b2 = selectedBlock[1];
		
		if(b1 != null && b2 != null){
			b1.setSelected(false);
			b2.setSelected(false);	
			state = Animation.SWITCH;
		}
	}
	
	public boolean fallBlocksAction(int delta){
		// all blocks with no block down to their position are moving to the next down freedown place
		boolean result = false;
		calculateFallingBlocks();
		for(int i = 0; i<ROWS; i++){
			for(int j = 0; j<COLUMNS; j++){
				result = result || fallingBlock(delta, blocks[j][i]);
			}
		}
		return result;
	}
	
	public void idleBlocksAction(){
		// sepcific Animation during idle time
		System.out.println("IDLE");
	}
	
	/**
	 * remove all blocks marked as "remove" from the array of blocks
	 * @return
	 */
	public void removeBlocksMatch(){
		clearSelectedBlock();
		// remove + animation of blocks that are matching
		System.out.println("remove + animation of blocks that are matching");
		for(int i = 0; i<ROWS; i++){
			for(int j = 0; j<COLUMNS; j++){
				if(blocks[j][i]!=null && blocks[j][i].isToRemove()) {
					blocks[j][i] = null;
				};
			}
		}
	}
	
	private boolean fallingBlock(int delta, Block b) {
		if(b!=null && b.isFalling()) {
			float deltaY = delta * 0.04f;
			b.moveDeltaY(deltaY);
			return true;
		}
		return false;
	}
	
	/**
	 * move UP or Down / Left or Right the selected blocks
	 * Return FALSE when the moving is DONE otherwise TRUE if the moving is still under process
	 */
	public boolean moveSelectedBlocks(int delta){
		Block b1 = selectedBlock[0];
		Block b2 = selectedBlock[1];
		float deltaXY = delta * 0.2f;
		if(b1.getColumn() == b2.getColumn()) {
			if(b1.getRow()<b2.getRow()) {
				blocks[b1.getColumn()][b1.getRow()].moveDeltaY(deltaXY);
				blocks[b2.getColumn()][b2.getRow()].moveDeltaY(-deltaXY);
			} else {
				blocks[b1.getColumn()][b1.getRow()].moveDeltaY(-deltaXY);
				blocks[b2.getColumn()][b2.getRow()].moveDeltaY(deltaXY);
			}
		} else if (b1.getRow() == b2.getRow()){
			if(b1.getColumn()<b2.getColumn()){
				blocks[b1.getColumn()][b1.getRow()].moveDeltaX(deltaXY);
				blocks[b2.getColumn()][b2.getRow()].moveDeltaX(-deltaXY);
			} else {
				blocks[b1.getColumn()][b1.getRow()].moveDeltaX(-deltaXY);
				blocks[b2.getColumn()][b2.getRow()].moveDeltaX(deltaXY);
			}
		}
		if(blocks[b1.getColumn()][b1.getRow()].isReachPosition() || blocks[b2.getColumn()][b2.getRow()].isReachPosition()){
			int b1Row = b1.getRow();
			int b1Column = b1.getColumn();
			int b2Row = b2.getRow();
			int b2Column = b2.getColumn();
			b2.setColumn(b1Column);
			b2.setRow(b1Row);
			b1.setColumn(b2Column);
			b1.setRow(b2Row);
			blocks[b1Column][b1Row] = b2;
			blocks[b2Column][b2Row] = b1;
			return false;
		} else {
			return true;
		}
		
	}
	
	/**
	 * put back the blocks to their original places - True is on going / false moving has stop, it is done
	 * @return
	 */
	public boolean moveBackSelectedBlocks(int delta){
		System.out.println("moveBACKSelectedBlocks");
		
		Block b1 = selectedBlock[0];
		Block b2 = selectedBlock[1];
		float deltaXY = delta * 0.1f;
		if(b1.getColumn() == b2.getColumn()) {
			if(b1.getRow()<b2.getRow()) {
				blocks[b1.getColumn()][b1.getRow()].moveDeltaY(deltaXY);
				blocks[b2.getColumn()][b2.getRow()].moveDeltaY(-deltaXY);
			} else {
				blocks[b1.getColumn()][b1.getRow()].moveDeltaY(-deltaXY);
				blocks[b2.getColumn()][b2.getRow()].moveDeltaY(deltaXY);
			}
		} else if (b1.getRow() == b2.getRow()){
			if(b1.getColumn()<b2.getColumn()){
				blocks[b1.getColumn()][b1.getRow()].moveDeltaX(deltaXY);
				blocks[b2.getColumn()][b2.getRow()].moveDeltaX(-deltaXY);
			} else {
				blocks[b1.getColumn()][b1.getRow()].moveDeltaX(-deltaXY);
				blocks[b2.getColumn()][b2.getRow()].moveDeltaX(deltaXY);
			}
		}
		if(blocks[b1.getColumn()][b1.getRow()].isReachPosition() || blocks[b2.getColumn()][b2.getRow()].isReachPosition()){
			int b1Row = b1.getRow();
			int b1Column = b1.getColumn();
			int b2Row = b2.getRow();
			int b2Column = b2.getColumn();
			b2.setColumn(b1Column);
			b2.setRow(b1Row);
			b1.setColumn(b2Column);
			b1.setRow(b2Row);
			blocks[b1Column][b1Row] = b2;
			blocks[b2Column][b2Row] = b1;
			clearSelectedBlock();
			return false;
		} else {
			return true;
		}
	}
	
	
	public void render(GameContainer container, StateBasedGame game, Graphics g)
			throws SlickException {
			for(int i = 0; i<ROWS; i++){
				for(int j = 0; j<COLUMNS; j++){
					if(blocks[j][i]!= null)
					blocks[j][i].render(container, game, g);
				}
			}
	}
	
	public void update(GameContainer container, StateBasedGame game, int delta) throws SlickException {
		
		if(container.getInput().isMousePressed(0)){
			int mouseX = container.getInput().getMouseX();
			int mouseY = container.getInput().getMouseY();
			// action player
			if(state.getId() == Animation.IDLE.getId() && isPlayerTurn){
				System.out.println("isPlayerTurn = "+isPlayerTurn);
				// Only if the gamer can select new blocks and if his turn to play
				if(mouseClickAction(mouseX, mouseY)){
					System.out.println("= clcik = ");
					// means 2 selections of block done
					// switch the block
					switchSelectedBlocksAction();
				}  				
			} // IDLE and OK TO PLAY
		} // Mouse clicked
		
		switch(state) {
		case SWITCH : // we do the animation of the selected block switching
			if(!moveSelectedBlocks(delta)) {
				// as soon as the moving stop
				if(
				findMatchesAndSetBlockToRemove(blocks, selectedBlock[0].getRow(), selectedBlock[0].getColumn()) ||  
				findMatchesAndSetBlockToRemove(blocks, selectedBlock[1].getRow(), selectedBlock[1].getColumn()) ){
					// if success, we change to state GOODMOVE
					state = Animation.GOODMOVE;				
				} else {
					// if not, we switch back
					wrong.play(1.0f,0.6f);		
					selectedBlock[0].resetDelta();
					selectedBlock[1].resetDelta();
					state = Animation.SWITCHBACK;
				}
			}
			break;
		case SWITCHBACK : // bad Move, we put back the blocks to their places
			if(!moveBackSelectedBlocks(delta)){
				state = Animation.IDLE;
			}
			break;
		case GOODMOVE : // bad Move, we put back the blocks to their places
			removeBlocksMatch();
			// should play animation of 'pop'
			state = Animation.FALL;
			break;
		case FALL : // bad Move, we put back the blocks to their places
			if(!fallBlocksAction(delta)){
				state = Animation.IDLE;
			}
			// should play animation of 'pop'
			break;
		}
		
	}
	
	// *********************** INTERNAL
	
	/**
	 * the array is the array already switched, but not yet calculated, return true if there is a match and SET the block to remove to true
	 * row and column is the place where we want to check if it matches. Not the full blocks array
	 * @param underProcessBlocks
	 * @return
	 */
	private boolean findMatchesAndSetBlockToRemove(Block[][] underProcessBlocks, final int row, final int column ){
		boolean isSuccess = false;
		int maxRowIndex = underProcessBlocks[0].length-1;
		int maxColumnIndex = underProcessBlocks.length-1;
		// test on column
		if(row-2>=0){
			// [ ] 1 case check ok 
			// [ ]
			// [.]
			isSuccess = underProcessBlocks[column][row].getType().getId() == underProcessBlocks[column][row-1].getType().getId() 
					&& underProcessBlocks[column][row].getType().getId()==underProcessBlocks[column][row-2].getType().getId();
			if(isSuccess) {
				underProcessBlocks[column][row].setToRemove(true);
				underProcessBlocks[column][row-1].setToRemove(true);
				underProcessBlocks[column][row-2].setToRemove(true);
				return true;
			}
		}
		if(row-1>=0 && row+1<=maxRowIndex){
			// [ ] 2 case check ok
			// [.]
			// [ ]
			isSuccess = underProcessBlocks[column][row].getType().getId() == underProcessBlocks[column][row-1].getType().getId() 
					&& underProcessBlocks[column][row].getType().getId()==underProcessBlocks[column][row+1].getType().getId();
			if(isSuccess) {
				underProcessBlocks[column][row].setToRemove(true);
				underProcessBlocks[column][row-1].setToRemove(true);
				underProcessBlocks[column][row+1].setToRemove(true);
				return true;
			}
		}
		if(row+2<=maxRowIndex){
			// [.] 3 case check ok
			// [ ]
			// [ ]
			isSuccess = underProcessBlocks[column][row].getType().getId() == underProcessBlocks[column][row+1].getType().getId() 
					&& underProcessBlocks[column][row].getType().getId()==underProcessBlocks[column][row+2].getType().getId();
			if(isSuccess)  {
				underProcessBlocks[column][row].setToRemove(true);
				underProcessBlocks[column][row+1].setToRemove(true);
				underProcessBlocks[column][row+2].setToRemove(true);
				return true;
			}
		}
		// test on row
		if (column-2>=0){
			// [ ][ ][.] 1 case is ok
			isSuccess = underProcessBlocks[column][row].getType().getId() == underProcessBlocks[column-2][row].getType().getId() 
					&& underProcessBlocks[column][row].getType().getId()==underProcessBlocks[column-1][row].getType().getId();
			if(isSuccess) {
				underProcessBlocks[column][row].setToRemove(true);
				underProcessBlocks[column-2][row].setToRemove(true);
				underProcessBlocks[column-1][row].setToRemove(true);
				return true;
			}
		}
		if(column-1>=0 && column+1<=maxColumnIndex){
			// [ ][.][ ] 2 case ok
			isSuccess = underProcessBlocks[column][row].getType().getId() == underProcessBlocks[column-1][row].getType().getId() 
					&& underProcessBlocks[column][row].getType().getId()==underProcessBlocks[column+1][row].getType().getId();
			if(isSuccess) { 
				underProcessBlocks[column][row].setToRemove(true);
				underProcessBlocks[column-1][row].setToRemove(true);
				underProcessBlocks[column+1][row].setToRemove(true);
				return true;
				}
			
		}
		if(column+2<=maxColumnIndex){
			// [.][ ][ ] 3 case ok
			isSuccess = underProcessBlocks[column][row].getType().getId() == underProcessBlocks[column+1][row].getType().getId() 
					&& underProcessBlocks[column][row].getType().getId()==underProcessBlocks[column+2][row].getType().getId();
			if(isSuccess) {
				underProcessBlocks[column][row].setToRemove(true);
				underProcessBlocks[column+1][row].setToRemove(true);
				underProcessBlocks[column+2][row].setToRemove(true);
				return true;
			}
		}
		return false;
	}
	
	private Block retrievedBlockFromClick(int mouseX, int mouseY) {
		if(mouseX>=position.x && mouseX<=position.x+blocks.length * BlockType.width && 
		   mouseY>=position.y && mouseY<=position.y+blocks[0].length * BlockType.height) {
			int column = new Double(Math.floor(( (float)mouseX - position.x ) /  BlockType.width)).intValue();
			int row    = new Double(Math.floor(( (float)mouseY - position.y ) /  BlockType.height)).intValue();
//			System.out.println("["+((float)mouseX - position.x)+"/ "+ BlockType.width+"]");
//			System.out.println("["+((float)mouseY - position.y)+"/ "+ BlockType.height+"]");
//			System.out.println("["+column+", "+row+"]");
			return blocks[column][row];
		} else 
			return null;
	}
	
	/**
	 * clear the selected block array
	 */
	private void clearSelectedBlock(){
		for(Block b : selectedBlock){
			if(b!=null)
			b.setSelected(false);
		}
		selectedBlock = new Block[2];
	}
	
	private boolean isContiguousBlock(Block b1, Block b2){
		System.out.println(b1 + " " + b2);
		return (b1.getColumn()>=b2.getColumn()-1) && (b1.getColumn()<=b2.getColumn()+1) 
		&& (b1.getRow()>=b2.getRow()-1) && (b1.getRow()<=b2.getRow()+1); 
	}
	
	/**
	 * returns true only if there is at least one black that is still falling
	 * @return
	 */
	private boolean calculateFallingBlocks(){
		boolean result = false;
		for(int i = ROWS-2; i>=0; i--){		
			for(int j = 0; j<COLUMNS; j++){
				if(blocks[j][i]!= null) {
					blocks[j][i].setFalling(blocks[j][i+1]==null || blocks[j][i+1].isFalling());
					if(!result) result = blocks[j][i].isFalling();	
				}
			}
		}
		return result;
	}

}
